#include <stdio.h>
#include "usart.h"
#include "lcd_driver.h"
#include "tim.h"

uint16_t POINT_COLOR = BLACK;
uint16_t BACK_COLOR = WHITE;
_lcd_dev lcddev;

static void Spi_write_cmd(uint8_t data);
static void Spi_write_data(uint8_t data);
static void LCD_WriteColorRepeat(uint16_t color, uint32_t count);

void Lcd_Gpio_Init(void)
{
    LCD_CS_1;
    LCD_DC_1;
    LCD_RST_1;
}

static void Lcd_Reset(void)
{
    LCD_RST_1;
    HAL_Delay(1U);
    LCD_RST_0;
    HAL_Delay(80U);
    LCD_RST_1;
    HAL_Delay(10U);
}

/* TIM3 CH2 period is 999, so value should normally be 0..999. */
void BlackLight_SetValue(uint16_t value)
{
    TIM_OC_InitTypeDef sConfigOC = {0};

    if (value > __HAL_TIM_GET_AUTORELOAD(&htim3))
    {
        value = (uint16_t)__HAL_TIM_GET_AUTORELOAD(&htim3);
    }

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = value;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;

    if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) == HAL_OK)
    {
        (void)HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
    }
}

static void Spi_write_cmd(uint8_t data)
{
    LCD_CS_0;
    LCD_DC_0;
    (void)HAL_SPI_Transmit(&hspi1, &data, 1U, 0xFFU);
    LCD_CS_1;
}

static void Spi_write_data(uint8_t data)
{
    LCD_CS_0;
    LCD_DC_1;
    (void)HAL_SPI_Transmit(&hspi1, &data, 1U, 0xFFU);
    LCD_CS_1;
}

/* ILI9341 is configured for RGB565 (COLMOD=0x55), high byte first. */
void Spi_write_word(uint16_t data)
{
    uint8_t tx[2];
    tx[0] = (uint8_t)(data >> 8);
    tx[1] = (uint8_t)data;
    (void)HAL_SPI_Transmit(&hspi1, tx, 2U, 0xFFU);
}

static void LCD_WriteColorRepeat(uint16_t color, uint32_t count)
{
    uint8_t block[128U * 2U];
    uint32_t i;
    uint16_t pixels;

    for (i = 0U; i < 128U; ++i)
    {
        block[i * 2U] = (uint8_t)(color >> 8);
        block[i * 2U + 1U] = (uint8_t)color;
    }

    while (count != 0U)
    {
        pixels = (count > 128U) ? 128U : (uint16_t)count;
        (void)HAL_SPI_Transmit(&hspi1, block, (uint16_t)(pixels * 2U), 0xFFFFU);
        count -= pixels;
    }
}

void Lcd_Init(void)
{
    Lcd_Gpio_Init();
    Lcd_Reset();
    HAL_Delay(120U);

    Spi_write_cmd(0x01U);                 /* Software reset */
    HAL_Delay(120U);
    Spi_write_cmd(0x11U);                 /* Sleep out */
    HAL_Delay(120U);

    Spi_write_cmd(0xCFU);
    Spi_write_data(0x00U);
    Spi_write_data(0xC1U);
    Spi_write_data(0x30U);

    Spi_write_cmd(0xEDU);
    Spi_write_data(0x64U);
    Spi_write_data(0x03U);
    Spi_write_data(0x12U);
    Spi_write_data(0x81U);

    Spi_write_cmd(0xE8U);
    Spi_write_data(0x85U);
    Spi_write_data(0x00U);
    Spi_write_data(0x79U);

    Spi_write_cmd(0xCBU);
    Spi_write_data(0x39U);
    Spi_write_data(0x2CU);
    Spi_write_data(0x00U);
    Spi_write_data(0x34U);
    Spi_write_data(0x02U);

    Spi_write_cmd(0xF7U);
    Spi_write_data(0x20U);

    Spi_write_cmd(0xEAU);
    Spi_write_data(0x00U);
    Spi_write_data(0x00U);

    Spi_write_cmd(0xC0U);
    Spi_write_data(0x1DU);

    Spi_write_cmd(0xC1U);
    Spi_write_data(0x12U);

    Spi_write_cmd(0xC5U);
    Spi_write_data(0x33U);
    Spi_write_data(0x3FU);

    Spi_write_cmd(0xC7U);
    Spi_write_data(0x92U);

    Spi_write_cmd(0x3AU);                 /* Pixel format */
    Spi_write_data(0x55U);                /* 16-bit RGB565 */

    Spi_write_cmd(0x36U);
    Spi_write_data(0x08U);

    Spi_write_cmd(0xB1U);
    Spi_write_data(0x00U);
    Spi_write_data(0x12U);

    Spi_write_cmd(0xB6U);
    Spi_write_data(0x0AU);
    Spi_write_data(0xA2U);

    Spi_write_cmd(0x44U);
    Spi_write_data(0x02U);

    Spi_write_cmd(0xF2U);
    Spi_write_data(0x00U);

    Spi_write_cmd(0x26U);
    Spi_write_data(0x01U);

    Spi_write_cmd(0xE0U);
    Spi_write_data(0x0FU);
    Spi_write_data(0x22U);
    Spi_write_data(0x1CU);
    Spi_write_data(0x1BU);
    Spi_write_data(0x08U);
    Spi_write_data(0x0FU);
    Spi_write_data(0x48U);
    Spi_write_data(0xB8U);
    Spi_write_data(0x34U);
    Spi_write_data(0x05U);
    Spi_write_data(0x0CU);
    Spi_write_data(0x09U);
    Spi_write_data(0x0FU);
    Spi_write_data(0x07U);
    Spi_write_data(0x00U);

    Spi_write_cmd(0xE1U);
    Spi_write_data(0x00U);
    Spi_write_data(0x23U);
    Spi_write_data(0x24U);
    Spi_write_data(0x07U);
    Spi_write_data(0x10U);
    Spi_write_data(0x07U);
    Spi_write_data(0x38U);
    Spi_write_data(0x47U);
    Spi_write_data(0x4BU);
    Spi_write_data(0x0AU);
    Spi_write_data(0x13U);
    Spi_write_data(0x06U);
    Spi_write_data(0x30U);
    Spi_write_data(0x38U);
    Spi_write_data(0x0FU);

    Spi_write_cmd(0x11U);
    HAL_Delay(120U);
    Spi_write_cmd(0x29U);

    LCD_Display_Dir(DFT_SCAN_DIR);
    BlackLight_SetValue(800U);
    LCD_Clear(BACKGROUND_COLOR);
    printf("2.4-inch ILI9341 LCD (without touch) init finished\r\n");
}

void LCD_Scan_Dir(uint8_t dir)
{
    uint8_t regval = 0U;

    switch (dir)
    {
        case L2R_U2D: regval = 0U; break;
        case L2R_D2U: regval = (1U << 7); break;
        case R2L_U2D: regval = (1U << 6); break;
        case R2L_D2U: regval = (1U << 7) | (1U << 6); break;
        case U2D_L2R: regval = (1U << 5); break;
        case U2D_R2L: regval = (1U << 7) | (1U << 5); break;
        case D2U_L2R: regval = (1U << 6) | (1U << 5); break;
        case D2U_R2L: regval = (1U << 7) | (1U << 6) | (1U << 5); break;
        default:      regval = (1U << 5); break;
    }

    regval |= 0x08U;                     /* BGR order */
    Spi_write_cmd(0x36U);
    Spi_write_data(regval);
}

void LCD_Display_Dir(uint8_t dir)
{
    if (dir <= R2L_D2U)
    {
        lcddev.dir = 0U;
        lcddev.width = LCD_H;
        lcddev.height = LCD_W;
    }
    else
    {
        lcddev.dir = 1U;
        lcddev.width = LCD_W;
        lcddev.height = LCD_H;
    }

    lcddev.wramcmd = 0x2CU;
    lcddev.setxcmd = 0x2AU;
    lcddev.setycmd = 0x2BU;
    LCD_Scan_Dir(dir);
}

void Set_Window(uint16_t start_x, uint16_t start_y, uint16_t end_x, uint16_t end_y)
{
    if ((end_x <= start_x) || (end_y <= start_y))
    {
        return;
    }

    Spi_write_cmd(lcddev.setxcmd);
    Spi_write_data((uint8_t)(start_x >> 8));
    Spi_write_data((uint8_t)start_x);
    Spi_write_data((uint8_t)((end_x - 1U) >> 8));
    Spi_write_data((uint8_t)(end_x - 1U));

    Spi_write_cmd(lcddev.setycmd);
    Spi_write_data((uint8_t)(start_y >> 8));
    Spi_write_data((uint8_t)start_y);
    Spi_write_data((uint8_t)((end_y - 1U) >> 8));
    Spi_write_data((uint8_t)(end_y - 1U));

    Spi_write_cmd(lcddev.wramcmd);
}

void LCD_Set_Cursor(uint16_t x, uint16_t y)
{
    Set_Window(x, y, (uint16_t)(x + 1U), (uint16_t)(y + 1U));
}

void LCD_Clear(uint16_t color)
{
    Set_Window(0U, 0U, lcddev.width, lcddev.height);
    LCD_CS_0;
    LCD_DC_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_4);
    LCD_WriteColorRepeat(color, (uint32_t)lcddev.width * lcddev.height);
    LCD_CS_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_16);
}

void LCD_Clearx(void)
{
    uint32_t stripe = ((uint32_t)lcddev.width * lcddev.height) / 3U;

    Set_Window(0U, 0U, lcddev.width, lcddev.height);
    LCD_CS_0;
    LCD_DC_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_4);
    LCD_WriteColorRepeat(RED, stripe);
    LCD_WriteColorRepeat(GREEN, stripe);
    LCD_WriteColorRepeat(BLUE, ((uint32_t)lcddev.width * lcddev.height) - stripe * 2U);
    LCD_CS_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_16);
}

void Gui_fill_color(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
    uint16_t temp;
    uint32_t count;

    if (x1 > x2)
    {
        temp = x1; x1 = x2; x2 = temp;
    }
    if (y1 > y2)
    {
        temp = y1; y1 = y2; y2 = temp;
    }

    if (x2 > lcddev.width)  x2 = lcddev.width;
    if (y2 > lcddev.height) y2 = lcddev.height;
    if ((x2 <= x1) || (y2 <= y1)) return;

    Set_Window(x1, y1, x2, y2);
    LCD_CS_0;
    LCD_DC_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_4);
    count = (uint32_t)(x2 - x1) * (uint32_t)(y2 - y1);
    LCD_WriteColorRepeat(color, count);
    LCD_CS_1;
    SPI_Set_Speed(SPI_BAUDRATEPRESCALER_16);
}

void Draw_Point(uint16_t x, uint16_t y, uint16_t color)
{
    if ((x >= lcddev.width) || (y >= lcddev.height))
    {
        return;
    }

    LCD_Set_Cursor(x, y);
    LCD_CS_0;
    LCD_DC_1;
    Spi_write_word(color);
    LCD_CS_1;
}
